﻿' ***********************************************************************
' Assembly         : Ioc
' Author           : Tim Ford
' Created          : 10-02-2013
'
' Last Modified By : Tim Ford
' Last Modified On : 10-02-2013
' ***********************************************************************
' <copyright file="ObjectFactory.vb" company="Red Baron Solutions">
'     Red Baron Solutions. All rights reserved.
' </copyright>
' <summary></summary>
' ***********************************************************************
Imports System.Reflection
Imports System.Reflection.Emit

Namespace Ioc
    ''' <summary>
    ''' 	
    ''' </summary>
    Public NotInheritable Class ObjectFactory

        ' Private constructor to prevent instantiation
        Private Sub New()
        End Sub

        Shared Sub New()
            m_LockingObject = New Object
        End Sub



        ''' <summary>
        ''' Creates the generic item.
        ''' </summary>
        ''' <param name="type">The type.</param>
        ''' <param name="genericType">The generic types.</param>
        ''' <param name="constructorObjects">The constructor objects.</param>
        ''' <returns>System.Object.</returns>
        Public Shared Function CreateGenericItem(type As Type, genericType As Type, ParamArray constructorObjects As Object()) As Object
            Return Activator.CreateInstance(type.MakeGenericType(genericType), constructorObjects)
        End Function



        ''' <summary>
        ''' Creates the specified target type.	
        ''' </summary>
        ''' <param name="targetType">Type of the target.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function Create(ByVal targetType As Type) As Object
            Dim targetObject As Object
            Try
                targetObject = GetNewInstance(targetType)
            Catch ex As System.Exception
                ' get the default constructor and instantiate
                Dim types(-1) As Type
                Dim info As ConstructorInfo = targetType.GetConstructor(types)
                If info IsNot Nothing Then
                    targetObject = info.Invoke(Nothing)
                    If targetObject Is Nothing Then
                        Throw New ArgumentException("Can't instantiate type " + targetType.Name)
                    End If


                Else
                    targetObject = Nothing
                End If
            End Try



            Return targetObject
        End Function

        ''' <summary>
        ''' Creates the specified type.	
        ''' </summary>
        ''' <param name="type">The type.</param>
        ''' <param name="params">The params.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function Create(ByVal type As Type, ParamArray params As Object()) As Object

            ' get the default constructor and instantiate
            Dim types As New List(Of Type)
            For Each item As Object In params
                types.Add(item.GetType())
            Next
            Dim info As ConstructorInfo = type.GetConstructor(types.ToArray)
            Dim targetObject As Object = info.Invoke(params)
            If targetObject Is Nothing Then
                Throw New ArgumentException("Can't instantiate type " + type.Name)
            End If

            Return targetObject
        End Function
        ''' <summary>
        ''' Creates the specified type name.	
        ''' </summary>
        ''' <param name="typeName">Name of the type.</param>
        ''' <param name="assembly">The assembly.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function Create(ByVal typeName As String, assembly As Assembly) As Object
            If Not String.IsNullOrWhiteSpace(typeName) AndAlso Not typeName.StartsWith(".", StringComparison.CurrentCulture) Then
                Dim targetType As Type = assembly.GetType(NoBrackets(typeName), False, False)
                Return Create(targetType)
            Else
                Return Nothing
            End If
        End Function

        ''' <summary>
        ''' Creates the specified type name.	
        ''' </summary>
        ''' <param name="typeName">Name of the type.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function Create(ByVal typeName As String) As Object

            ' resolve the type
            Dim targetType As Type = ResolveType(typeName)
            If targetType Is Nothing Then
                Throw New ArgumentException("Can't load type " + typeName)
            End If
            Return Create(targetType)

        End Function

        ''' <summary>
        ''' Gets the new instance.	
        ''' </summary>
        ''' <param name="type">The type.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function GetNewInstance(type As Type) As Object
            ' object statically
            Dim createInstance As CreateInstanceDelegate = ObjectInstantiater(type)
            If createInstance IsNot Nothing Then
                Return createInstance()
            End If
            Return Nothing
        End Function
        Private Shared m_LockingObject As Object
        ' Function that creates the method dynamically for creating the instance
        ' of a given class type
        Private Shared Function ObjectInstantiater(objectType As Type) As CreateInstanceDelegate
            Dim createInstanceDelegate As CreateInstanceDelegate
            If CreateInstanceDelegateList.ContainsKey(objectType) Then
                createInstanceDelegate = CreateInstanceDelegateList(objectType)
            End If

            SyncLock m_LockingObject
                ' Create a new method.        
                Dim dynamicMethod As New DynamicMethod(String.Format("Create_{0}", Convert.ToString(objectType.Name)), objectType, New Type(-1) {})

                ' Get the default constructor of the plugin type
                Dim ctor As ConstructorInfo = objectType.GetConstructor(New Type(-1) {})
                If ctor IsNot Nothing Then
                    ' Generate the intermediate language.       
                    Dim ilgen As ILGenerator = dynamicMethod.GetILGenerator()
                    ilgen.Emit(OpCodes.Newobj, ctor)
                    ilgen.Emit(OpCodes.Ret)

                    ' Create new delegate and store it in the dictionary
                    createInstanceDelegate = DirectCast(dynamicMethod.CreateDelegate(GetType(CreateInstanceDelegate)), CreateInstanceDelegate)
                    CreateInstanceDelegateList.TryAdd(objectType, createInstanceDelegate)
                    Return createInstanceDelegate
                End If

            End SyncLock

            Return Nothing
            ' return the object instantiator delegate
        End Function
        ''' <summary>
        ''' Resolves the type.	
        ''' </summary>
        ''' <param name="type">The type string.</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function ResolveType(ByVal type As String) As Type

            Dim commaIndex As Integer = type.IndexOf(",", StringComparison.CurrentCulture)
            Dim returning As Type
            If commaIndex < 1 Then
                For Each targetAssembly As Assembly In AppDomain.CurrentDomain.GetAssemblies()
                    Try
                        returning = targetAssembly.GetType(type, False, False)
                        If returning IsNot Nothing Then
                            Return returning
                        End If
                    Catch ex As System.Exception
                        Ioc.Container.GetInstance(Of IExceptionLogger).WriteException(ex)
                    End Try
                Next
                Return Nothing
            Else
                Dim targetAssembly As Assembly = Nothing
                Dim className As String = type.Substring(0, commaIndex).Trim()

                Dim assemblyName As String = type.Substring(commaIndex + 1).Trim()

                ' Get the assembly containing the handler

                Try
                    Return System.Type.GetType(type)
                    targetAssembly = Assembly.Load(assemblyName)
                Catch ex As Exception
                    If type.Contains("[[") Then
                        Return System.Type.GetType(type)
                    End If
                    Try
                        targetAssembly = [Assembly].Load(assemblyName)
                    Catch exp As Exception
                        Throw New ArgumentException("Can't load assembly " + assemblyName)
                    End Try
                End Try

                ' Get the type
                Return targetAssembly.GetType(className, False, False)
            End If

        End Function
        Private Delegate Function CreateInstanceDelegate() As Object

        Private Shared Function CreateInstanceDelegateList() As System.Collections.Concurrent.ConcurrentDictionary(Of Type, CreateInstanceDelegate)
            If m_CreateInstanceDelegateList Is Nothing Then
                m_CreateInstanceDelegateList = New System.Collections.Concurrent.ConcurrentDictionary(Of Type, CreateInstanceDelegate)()
            End If
            Return m_CreateInstanceDelegateList
        End Function
        Private Shared m_CreateInstanceDelegateList As System.Collections.Concurrent.ConcurrentDictionary(Of Type, CreateInstanceDelegate)

        Private Shared Function NoBrackets(ByVal value As String) As String
            If Not String.IsNullOrWhiteSpace(value) Then
                Return value.Replace("[", "").Replace("]", "").Replace("{", "").Replace("}", "").Replace("(", "").Replace(")", "")
            End If
            Return value
        End Function
    End Class
End Namespace
